package com.example.utils;

import com.google.common.base.Preconditions;
import net.coobird.thumbnailator.Thumbnails;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.lang.Nullable;
import org.springframework.util.Base64Utils;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashSet;
import java.util.Set;


/**
 * @ClassName ImageUtil
 * @Description Image util
 * @Auther maolingwei
 * @Date 2020-01-15 14:25
 * @Version 1.0
 **/
public class ImageUtil {

	private static final Logger log = LoggerFactory.getLogger(ImageUtil.class);

	private ImageUtil() {
		throw new IllegalStateException();
	}

	private static final String BASE64_IMAGE_HEADER_PREFIX = "data:image/";
	private static final String BASE64_IMAGE_HEADER_SUFFIX = ";base64";

	private static final Set<String> BASE64_SUPPORTED_IMAGE_FORMATS;
	static {
		BASE64_SUPPORTED_IMAGE_FORMATS = new HashSet<>();
		BASE64_SUPPORTED_IMAGE_FORMATS.add("gif");
		BASE64_SUPPORTED_IMAGE_FORMATS.add("png");
		BASE64_SUPPORTED_IMAGE_FORMATS.add("jpeg");
		BASE64_SUPPORTED_IMAGE_FORMATS.add("jpg");
		BASE64_SUPPORTED_IMAGE_FORMATS.add("x-icon");
	}

	/**
	 * resize image
	 *
	 * @param base64Image img
	 * @param expectedSize unit KB
	 * @return compressed img
	 */
	public static String resizeBase64Image(String base64Image, int expectedSize)
			throws IOException {
		Preconditions.checkNotNull(base64Image);
		if (expectedSize <= 0) {
			throw new IllegalArgumentException("expected size should larger than 0");
		}

		String[] split = base64Image.split(",");
		if (split.length != 2) {
			throw new IllegalArgumentException("Illegal base64 image, length is " + split.length);
		}

		String base64ImageHeader = split[0];
		String base64ImageData = split[1];
		int size = getBase64ImageSizeByData(base64ImageData);
		if (size <= expectedSize) {
			return base64Image;
		}
		// 获取base64图片的格式，jpg或者png或者其他
		String base64ImageFormat = getBase64ImageFormatByHeader(base64ImageHeader);

		double firstScale = Math.sqrt(expectedSize * 1.0 / size);

		BufferedImage src = base64ImageDataToBufferedImage(base64ImageData);
		src = ensureOpaque(src, base64ImageFormat);
		BufferedImage out = Thumbnails.of(src).scale(firstScale).asBufferedImage();
		String base64 = bufferedImageToBase64Image(out, base64ImageFormat);
		while (getBase64ImageSize(base64) > expectedSize) {
			out = Thumbnails.of(out).scale(0.9).asBufferedImage();
			base64 = bufferedImageToBase64Image(out, base64ImageFormat);
		}

		return base64;
	}

    /**
     * 将一个Base64 以图片的形式存入本地磁盘
     */
	public static void saveBase64(String base64, String format, String path) throws IOException {
		BASE64Decoder decoder = new BASE64Decoder();
		String[] split = base64.split(",");
		byte[] imageByte = decoder.decodeBuffer(split[1]);
		ByteArrayInputStream bis = new ByteArrayInputStream(imageByte);
		BufferedImage image = ImageIO.read(bis);
		image = ensureOpaque(image, format);
		bis.close();

		// write the image to a file
		File output = new File(path);
		ImageIO.write(image, format, output);
	}

    /**
     * jpg无法识别png的透明像素,因此对这些透明区域进行处理
     */
	static BufferedImage ensureOpaque(BufferedImage bi, String format) {
		if (format == null
				|| (!format.equalsIgnoreCase("jpg") && !format.equalsIgnoreCase("jpeg"))) {
			return bi;
		}
		if (bi.getTransparency() == BufferedImage.OPAQUE)
			return bi;
		int w = bi.getWidth();
		int h = bi.getHeight();
		int[] pixels = new int[w * h];
		bi.getRGB(0, 0, w, h, pixels, 0, w);
		BufferedImage bi2 = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
		bi2.setRGB(0, 0, w, h, pixels, 0, w);
		return bi2;
	}

	public static String getBase64ImageFormat(String base64Image) {
		String base64ImageHeader = getBase64ImageHeader(base64Image);
		return getBase64ImageFormatByHeader(base64ImageHeader);
	}

	public static String getBase64ImageFormatByHeader(String base64ImageHeader) {
		int index1 = base64ImageHeader.indexOf(BASE64_IMAGE_HEADER_PREFIX);
		int index2 = base64ImageHeader.indexOf(BASE64_IMAGE_HEADER_SUFFIX);
		if (index1 == -1 || index2 == -1 || index1 >= index2) {
			throw new IllegalArgumentException("Illegal argument base64ImageHeader: " + base64ImageHeader);
		}

		return base64ImageHeader.substring(BASE64_IMAGE_HEADER_PREFIX.length(), index2);
	}

	public static BufferedImage base64ImageToBufferedImage(String base64Image) {
		String base64ImageData = getBase64ImageData(base64Image);
		return base64ImageDataToBufferedImage(base64ImageData);
	}

	@Nullable
	public static BufferedImage base64ImageDataToBufferedImage(String base64ImageData) {
		Preconditions.checkNotNull(base64ImageData);
		BufferedImage image = null;
		try (InputStream stream = base64ImageDataToInputStream(base64ImageData)) {
			if (stream == null) {
				return null;
			}
			image = inputStreamToBufferedImage(stream);
		} catch (IOException ex) {
			log.error("base64DataToBufferedImage error", ex);
		}

		return image;
	}

	public static BufferedImage inputStreamToBufferedImage(InputStream is) {
		Preconditions.checkNotNull(is);
		BufferedImage image = null;
		try {
			image = ImageIO.read(is);
		} catch (IOException ex) {
			log.error("inputStreamToBufferedImage error", ex);
		}
		return image;
	}

	@Nullable
	private static InputStream base64ImageDataToInputStream(String base64ImageData) {
		Preconditions.checkNotNull(base64ImageData);
		ByteArrayInputStream stream = null;
		try {
			BASE64Decoder decoder = new BASE64Decoder();
			byte[] buf = decoder.decodeBuffer(base64ImageData);
			stream = new ByteArrayInputStream(buf);
		} catch (Exception ex) {
			log.error("base64ImageDataToInputStream", ex);
		}
		return stream;
	}

	@Nullable
	public static String bufferedImageToBase64ImageAsPng(BufferedImage bufferedImage) {
		return bufferedImageToBase64Image(bufferedImage, "png");
	}

	@Nullable
	public static String bufferedImageToBase64Image(BufferedImage bufferedImage, String format) {
		checkSupportedFormat(format);
		try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
			ImageIO.write(bufferedImage, format, outputStream);
			String data = Base64Utils.encodeToString(outputStream.toByteArray());
			return assembleBase64Image(format, data);
		} catch (IOException ex) {
			log.error("bufferedImageToBase64Image error", ex);
			return null;
		}
	}

	private static String inputStreamToBase64Image(InputStream inputStream) throws IOException {
		byte[] bytes = IOUtils.toByteArray(inputStream);
		return Base64Utils.encodeToString(bytes);
	}

	/**
	 * get base64 image size
	 * @param base64Image img
	 * @return size KB
	 */
	public static int getBase64ImageSize(String base64Image) {
		Preconditions.checkNotNull(base64Image);
		String base64ImageData = getBase64ImageData(base64Image);
		return getBase64ImageSizeByData(base64ImageData);
	}

	public static int getBase64ImageSizeByData(String base64ImageData) {
		// 找到等号，把等号去掉(=用来填充base64字符串长度用)
		int equalIndex = base64ImageData.indexOf('=');
		if (equalIndex > 0) {
			base64ImageData = base64ImageData.substring(0, equalIndex);
		}
		// 原来的字符流大小，单位为字节
		int length = base64ImageData.length();
		// 计算后得到的文件流大小，单位为KB
		return (length - length / 4) / 1024;
	}

	public static String replaceImgUrlToHttps(String imgUrl) {
		Preconditions.checkNotNull(imgUrl);
		if (imgUrl.startsWith("http://")) {
			return imgUrl.replaceFirst("http://", "https://");
		} else {
			return imgUrl;
		}
	}

	public static String getBase64ImageByUrlAsPng(String url) throws Exception {
		return getBase64ImageByUrl(url, "png");
	}

	public static String getBase64ImageByUrlAsJpeg(String url) throws Exception {
		return getBase64ImageByUrl(url, "jpeg");
	}

	public static String getBase64ImageByUrlAsGif(String url) throws Exception {
		return getBase64ImageByUrl(url, "gif");
	}

	public static String getBase64ImageByUrl(String url, String format) throws Exception {
		Preconditions.checkNotNull(url);
		URL url1 = new URL(url);
		return getBase64ImageByUrl(url1, format);
	}

	public static String getBase64ImageByUrl(URL url, String format) throws Exception {
		checkSupportedFormat(format);
		HttpURLConnection conn;
		try {
			conn = (HttpURLConnection) url.openConnection();
			conn.setRequestMethod("GET");
			conn.setConnectTimeout(5 * 1000);
			InputStream inStream = conn.getInputStream();
			ByteArrayOutputStream outStream = new ByteArrayOutputStream();
			byte[] buffer = new byte[1024];
			int len = 0;
			while ((len = inStream.read(buffer)) != -1) {
				outStream.write(buffer, 0, len);
			}
			inStream.close();
			byte[] data = outStream.toByteArray();
			BASE64Encoder encoder = new BASE64Encoder();
			String dataStr = encoder.encode(data); //返回Base64编码过的字节数组字符串
			return assembleBase64Image(format, dataStr);
		} catch (IOException e) {
			throw new Exception("Failed to get base64 from url", e);
		}
	}

	private static void checkSupportedFormat(String format) {
		if (!BASE64_SUPPORTED_IMAGE_FORMATS.contains(format)) {
			throw new IllegalArgumentException("Not supported image type: " + format);
		}
	}

	public static String getBase64ImageHeader(String base64Image) {
		return splitBase64Image(base64Image)[0];
	}

	public static String getBase64ImageData(String base64Image) {
		return splitBase64Image(base64Image)[1];
	}

	private static String[] splitBase64Image(String base64Image) {
		String[] split = base64Image.split(",");
		if (split.length != 2) {
			throw new IllegalArgumentException("Illegal base64 image, split length is " + split.length);
		}
		String header = split[0];
		checkBase64ImageHeader(header);
		return split;
	}

	private static void checkBase64ImageHeader(String base64ImageHeader) {
		Preconditions.checkNotNull(base64ImageHeader);
		String format = getBase64ImageFormatByHeader(base64ImageHeader);
		checkSupportedFormat(format);
	}

	public static String assembleBase64Image(String format, String data) {
		return generateBase64ImageHeader(format) + "," + data;
	}

	private static String generateBase64ImageHeader(String format) {
		return BASE64_IMAGE_HEADER_PREFIX + format + BASE64_IMAGE_HEADER_SUFFIX;
	}

	public static BufferedImage rotateImage(BufferedImage originImage, int rotateAngle) {
		if (rotateAngle <= 0) {
			return originImage;
		}

		int originWidth = originImage.getWidth();
		int originHeight = originImage.getHeight();
		int fixedWidth = originWidth;
		int fixedHeight = originHeight;

		if (rotateAngle == 90 || rotateAngle == 270) {
			fixedWidth = originHeight;
			fixedHeight = originWidth;
		}

		Rectangle rectangle = new Rectangle(new Dimension(fixedWidth, fixedHeight));
		BufferedImage res = new BufferedImage(rectangle.width, rectangle.height, BufferedImage.TYPE_INT_RGB);
		Graphics2D g2 = res.createGraphics();
		g2.translate((rectangle.width - originWidth) / 2, (rectangle.height - originHeight) / 2);
		g2.rotate(Math.toRadians(rotateAngle), originWidth / 2, originHeight / 2);
		g2.drawImage(originImage, null, null);

		return res;
	}

	public static BufferedImage resizeBufferedImage(BufferedImage image, int width, int height, boolean flag) {
		Preconditions.checkNotNull(image);
		if (width < 0 || height < 0) {
			throw new IllegalArgumentException("width and height must bigger than 0");
		}
		double sx = 1.0 * width / image.getWidth();
		double sy = 1.0 * height / image.getHeight();
		if (flag && sx > sy) {
			sx = sy;
			width = (int) (sx * image.getWidth());
		} else if (flag && sx < sy) {
			sy = sx;
			height = (int) (sy * image.getHeight());
		}

		BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
		Graphics graphics = bufferedImage.createGraphics();
		graphics.drawImage(image, 0, 0, width, height, null);
		graphics.dispose();
		return bufferedImage;
	}
}
